<?php
/**
 * This file houses the Handler class.
 *
 * Copyright (c) 2010, Kalkomey Enterprises, Inc.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * - Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, this 
 *   list of conditions and the following disclaimer in the documentation and/or other
 *   materials provided with the distribution.
 * - Neither the name of the Kalkomey Enterprises, Inc. nor the names of its 
 *   contributors may be used to endorse or promote products derived from this 
 *   software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 * @copyright Copyright (c) 2010, Kalkomey Enterprises, Inc.
 * @link http://code.google.com/p/swerv
 * @package Framework
 * @subpackage Library
 */

/**
 * The Handlers class holds all the methods needed to perform some basic error, exception, and autoloading handling automagically.
 *
 * These handlers are actually set in the init.php script.
 *
 * @package Framework
 * @subpackage Library
 */
class Handler {

	/**
	 * Error handler.  Halts script execution.
	 *
	 * @uses PATH_BASE
	 * @uses DEBUG_MODE
	 *
	 * @param int $number the number (type) of error
	 * @param string $message the error message
	 * @param string $file the name of the file where the error was triggered
	 * @param int $line the line number where the error was triggered
	 * @param array $context an array of all variables and data in memory at the time the error was triggered
	 *
	 * @return void
	 */
	public static function errors($number, $message, $file, $line, $context) {
		$errorTypes = array (
			E_ERROR => 'Fatal Error',
			E_WARNING => 'Warning',
			E_PARSE => 'Parsing Error',
			E_NOTICE => 'Notice',
			E_CORE_ERROR => 'Core Error',
			E_CORE_WARNING => 'Core Warning',
			E_COMPILE_ERROR => 'Compile Error',
			E_COMPILE_WARNING => 'Compile Warning',
			E_USER_ERROR => 'Fatal Error',
			E_USER_WARNING => 'Warning',
			E_USER_NOTICE => 'Notice',
			E_STRICT => 'PHP5 Strict Non-Compliance',
			E_RECOVERABLE_ERROR => 'Recoverable Error'
		);

		if (isset($errorTypes[$number])) {
			$errorType = $errorTypes[$number];
		} else {
			$errorType = 'Fatal Error';
		}

		$backTrace = debug_backtrace();
		$totalBackTrace = count($backTrace);
		$backList = '';

		if ($totalBackTrace > 1) {
			$backList = '<em>Back Trace:</em><ul>' . "\n";
			for($i = 0; $i < $totalBackTrace; $i++) {
				$item = $backTrace[$i];

				if (isset($backTrace[$i+1])) {
					$nextItem = $backTrace[$i+1];
				} else {
					$nextItem = array();
				}

				if (isset($item['line'])) {
					$btLine = $item['line'];
				} else {
					continue;
				}

				if (isset($nextItem['class'])) {
					$location = $nextItem['class'] . $nextItem['type'] . $nextItem['function'] . '()';
				} else if (isset($nextItem['function'])) {
					$location = $nextItem['function'] . '()';
				}

				if (isset($item['file'])) {
					$file = str_replace('\\', '/', $item['file']);
				} else {
					$file = 'unknown';
				}

				$backList .= "<li>Line <strong>$btLine</strong> in <strong>$location</strong> in file $file</li>\n";
			}
			$backList .= '</ul>' . "\n";
		}

		$date = date('m/d/Y - g:i A T');
		$currentUrl = '';

		if (isset($_SERVER) && isset($_SERVER['HTTP_HOST'])) {
			$currentUrl = ' - ' . htmlentities('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
		}

		$errorString = "\n" . '<div style="color: #AA0000; font-weight: bold; font-size: 14pt;">' . $errorType . ': ' . $message . '</div>' . "\n";
		$errorString .= '<br />' . $backList . $date . $currentUrl . '<br />' . "\n";
		$errorString = str_replace(PATH_BASE, '', $errorString);
		$html = '<?xml version="1.0" encoding="UTF-8"?>
		<?xml-stylesheet type="text/xsl" href="/xhtml.xsl"?>
		<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
		<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd" xml:lang="en">';

		if (defined('DEBUG_MODE') && DEBUG_MODE === false) {
			$html .= '<head>
						<title>500 Internal Server Error</title><meta http-equiv="expires" content="0" />
						<style type="text/css">
							html, body, table, div { font-family: "Verdana", sans-serif; font-size: 10pt; }
							html { background-color: #DDEEFF; }
						</style>
					 </head>
					 <body style="margin: 0;">
						 <div style="position: absolute; top: 20px; width: 80%; background-color: #AAAAFF;">
							 <div style="padding: 6px; font-size: 14pt; font-weight: bold;">It seems that you\'ve found an error.</div>
							 <div style="padding: 6px; padding-top: 3px; font-size: 10pt;">This has been reported to our Software Developers and will be resolved as quickly as possible.</div>
						 </div>
						<div style="font-size: 0; position: absolute; top: 100px; width: 70%; background-color: #9595FF; height: 9px;"></div>
						<div style="font-size: 0; position: absolute; top: 125px; width: 60%; background-color: #7575FF; height: 6px;"></div>
						<div style="font-size: 0; position: absolute; top: 150px; width: 50%; background-color: #6565FF; height: 3px;"></div>
						<div style="font-size: 0; position: absolute; top: 175px; width: 40%; background-color: #0000FF; height: 1px;"></div>
					 </body>';
		} else {
			$html .= '<head>
						<title>500 Internal Server Error</title><meta http-equiv="expires" content="0" />
						<style type="text/css">
							html, body, table, div { font-family: "Verdana", sans-serif; font-size: 10pt; }
						</style>
					 </head>
					 <body>
						 <div>' . $errorString . '</div>
					 </body>';
		}

		$html .= '</html>';

		$obStatus = ob_get_status();
		if (count($obStatus) > 0) {
			ob_clean();
		}

		$mime = "text/html";
		if (isset($_SERVER['HTTP_ACCEPT']) && stripos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
			$mime = "application/xhtml+xml";
		}

		if (!headers_sent()) {
			header('HTTP/1.1 500 Internal Server Error');
			header("Content-Type: $mime;charset=utf-8");
			header("Cache-Control: no-cache, must-revalidate");
			header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
			header("Vary: Accept");
			echo $html;
		}
		exit;
	}

	/**
	 * Error handler for command line scripts.  Halts script execution.
	 *
	 * @uses StringUtility::writeLine()
	 *
	 * @param int	$number  the number (type) of error
	 * @param string $message the error message
	 * @param string $file	the name of the file where the error was triggered
	 * @param int	$line	the line number where the error was triggered
	 * @param array  $context an array of all variables and data in memory at the time the error was triggered
	 *
	 * @return void
	 */
	public static function cliErrors($number, $message, $file, $line, $context) {
		$errorTypes = array (
			E_ERROR => 'Fatal Error',
			E_WARNING => 'Warning',
			E_PARSE => 'Parsing Error',
			E_NOTICE => 'Notice',
			E_CORE_ERROR => 'Core Error',
			E_CORE_WARNING => 'Core Warning',
			E_COMPILE_ERROR => 'Compile Error',
			E_COMPILE_WARNING => 'Compile Warning',
			E_USER_ERROR => 'Fatal Error',
			E_USER_WARNING => 'Warning',
			E_USER_NOTICE => 'Notice',
			E_STRICT => 'PHP5 Strict Non-Compliance',
			E_RECOVERABLE_ERROR => 'Recoverable Error'
		);

		if (isset($errorTypes[$number])) {
			$errorType = $errorTypes[$number];
		} else {
			$errorType = 'Fatal Error';
		}

		StringUtility::writeLine('');
		StringUtility::writeLine('=== ' . date('Y-m-d g:i:s') . ' ======================================================');
		StringUtility::writeLine('');
		StringUtility::writeLine($errorType . ': ' . $message . ' on line ' . $line);
		$backTrace = debug_backtrace();
		$backList = '';
		$totalBackTrace = count($backTrace);

		if ($totalBackTrace > 1) {
			StringUtility::writeLine('');
			StringUtility::writeLine('Back Trace:');
			foreach ($backTrace as $item) {

				if (isset($item['line'])) {
					$btLine = $item['line'];
				} else {
					continue;
				}

				if (isset($item['class'])) {
					$location = $item['class'] . $item['type'] . $item['function'] . '()';
				} else {
					$location = $item['function'] . '()';
				}

				if (isset($item['file'])) {
					$file = str_replace('\\', '/', $item['file']);
				} else {
					$file = 'unknown';
				}

				StringUtility::writeLine('');
				StringUtility::writeLine("Line $btLine using $location in file $file", 4);
			}
		}
		StringUtility::writeLine('');
		StringUtility::writeLine('===============================================================================');
		StringUtility::writeLine('');
		exit;
	}

	/**
	 * Uncaught exception handler.  Halts script execution.
	 *
	 * @uses PATH_BASE
	 * @uses DEBUG_MODE
	 * @uses StringUtility::pascalCaseToWords()
	 *
	 * @param Exception $exObj an Exception object
	 *
	 * @return void
	 */
	public static function exceptions($exObj) {
		$errorType = StringUtility::pascalCaseToWords(get_class($exObj));
		$line = $exObj->getLine();
		$file = $exObj->getFile();
		$message = $exObj->getMessage();

		// backtrace info
		$backTrace = $exObj->getTrace();
		$backList = '';
		$totalBackTrace = count($backTrace);
		if ($totalBackTrace > 0) {
			$backList = '<em>Back Trace:</em><ul>';
			for($i = 0; $i < $totalBackTrace; $i++) {
				$item = $backTrace[$i];

				if (isset($backTrace[$i+1])) {
					$nextItem = $backTrace[$i+1];
				} else {
					$nextItem = array();
				}

				if (isset($item['line'])) {
					$btLine = $item['line'];
				} else {
					continue;
				}

				if (isset($nextItem['class'])) {
					$location = $nextItem['class'] . $nextItem['type'] . $nextItem['function'] . '()';
				} else if (isset($nextItem['function'])) {
					$location = $nextItem['function'] . '()';
				}
				else {
					$location = 'global scope';
				}

				if (isset($item['file'])) {
					$file = str_replace('\\', '/', $item['file']);
				} else {
					$file = 'unknown';
				}

				$backList .= "<li>Line <strong>$btLine</strong> in <strong>$location</strong> in file $file</li>";
			}
			$backList .= '</ul>';
		} else {
			$backList = '<em>Back Trace:</em><ul>';
			$backList .= "<li>Line <strong>{$exObj->getLine()}</strong> in file {$exObj->getFile()}</li>";
			$backList .= '</ul>';
		}

		$date = date('m/d/Y - g:i A T');
		$currentUrl = '';

		if (isset($_SERVER) && isset($_SERVER['HTTP_HOST'])) {
			$currentUrl = ' - ' . htmlentities('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
		}

		$errorString = '<div style="color: #AA0000; font-weight: bold; font-size: 14pt;">Uncaught ' . $errorType . ': ' . $message;
		$errorString .= '</div><br />' . $backList . $date . $currentUrl . '<br />';
		$errorString = str_replace(PATH_BASE, '', $errorString);
		$html = '<?xml version="1.0" encoding="UTF-8"?>
		<?xml-stylesheet type="text/xsl" href="/xhtml.xsl"?>
		<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
		<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd" xml:lang="en">';

		if (defined('DEBUG_MODE') && DEBUG_MODE === false) {
			$html .= '<head>
						<title>500 Internal Server Error</title><meta http-equiv="expires" content="0" />
						<style type="text/css">
							html, body, table, div { font-family: "Verdana", sans-serif; font-size: 10pt; }
							html { background-color: #DDEEFF; }
						</style>
					 </head>
					 <body style="margin: 0;">
						 <div style="position: absolute; top: 20px; width: 80%; background-color: #AAAAFF;">
							 <div style="padding: 6px; font-size: 14pt; font-weight: bold;">It seems that you\'ve found an error.</div>
							 <div style="padding: 6px; padding-top: 3px; font-size: 10pt;">This has been reported to our Software Developers and will be resolved as quickly as possible.</div>
						 </div>
						<div style="font-size: 0; position: absolute; top: 100px; width: 70%; background-color: #9595FF; height: 9px;"></div>
						<div style="font-size: 0; position: absolute; top: 125px; width: 60%; background-color: #7575FF; height: 6px;"></div>
						<div style="font-size: 0; position: absolute; top: 150px; width: 50%; background-color: #6565FF; height: 3px;"></div>
						<div style="font-size: 0; position: absolute; top: 175px; width: 40%; background-color: #0000FF; height: 1px;"></div>
					 </body>';
		} else {
			$html .= '<head>
						<title>500 Internal Server Error</title><meta http-equiv="expires" content="0" />
						<style type="text/css">
							html, body, table, div { font-family: "Verdana", sans-serif; font-size: 10pt; }
						</style>
					 </head>
					 <body>
						 <div>' . $errorString . '</div>
					 </body>';
		}

		$html .= '</html>';
		$obStatus = ob_get_status();
		if (count($obStatus) > 0) {
			ob_clean();
		}

		$mime = "text/html";
		if (isset($_SERVER['HTTP_ACCEPT']) && stripos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
			$mime = "application/xhtml+xml";
		}

		if (!headers_sent()) {
			header('HTTP/1.1 500 Internal Server Error');
			header("Content-Type: $mime;charset=utf-8");
			header("Cache-Control: no-cache, must-revalidate");
			header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
			header("Vary: Accept");
			echo $html;
		}

		exit;
	}

	/**
	 * Uncaught exception handler for command line scripts.  Halts script execution.
	 *
	 * @uses StringUtility::writeLine()
	 * @uses StringUtility::pascalCaseToWords()
	 *
	 * @param Exception $exObj an Exception object
	 *
	 * @return void
	 */
	public static function cliExceptions($exObj)
	{
		$errorType = StringUtility::pascalCaseToWords(get_class($exObj));
		$line = $exObj->getLine();
		$file = $exObj->getFile();
		$message = $exObj->getMessage();

		StringUtility::writeLine('');
		StringUtility::writeLine('=== ' . date('Y-m-d g:i:s') . ' ======================================================');
		StringUtility::writeLine('');
		StringUtility::writeLine('Uncaught ' . $errorType . ': ' . $message);
		$backTrace = $exObj->getTrace();
		$backList = '';
		$totalBackTrace = count($backTrace);

		if ($totalBackTrace > 0) {
			StringUtility::writeLine('');
			StringUtility::writeLine('Back Trace:');
			foreach ($backTrace as $item) {

				if (isset($item['line'])) {
					$btLine = $item['line'];
				} else {
					continue;
				}

				if (isset($item['class'])) {
					$location = $item['class'] . $item['type'] . $item['function'] . '()';
				} else {
					$location = $item['function'] . '()';
				}

				if (isset($item['file'])) {
					$file = str_replace('\\', '/', $item['file']);
				} else {
					$file = 'unknown';
				}

				StringUtility::writeLine('');
				StringUtility::writeLine("Line $btLine using $location in file $file", 4);
			}
		} else {
			StringUtility::writeLine('');
			StringUtility::writeLine('Back Trace:');
			StringUtility::writeLine('');
			StringUtility::writeLine("Line {$exObj->getLine()} in file {$exObj->getFile()}", 4);
		}
		StringUtility::writeLine('');
		StringUtility::writeLine('===============================================================================');
		StringUtility::writeLine('');
		exit;
	}


	/**
	 * Autoloader; called when a class is used which is not yet defined in the current scope.
	 *
	 * @uses Cache
	 * @uses Cache::getInstance()
	 *
	 * @throws AutoloadException if class or interface cannot be found
	 *
	 * @param string $className the name of the class being used
	 * @param boolean $useInclude if set to true, this function will use include() instead of require_once() to load the class file
	 *
	 * @return void
	 */
	public static function autoloading($className, $useInclude = false) {
		// look in Cache first -- I only want these items cached for 5 days
		$cache = Cache::getInstance(60 * 60 * 24 * 5);

		// is the classLocations key already defined?  if not, let's create it
		try {
			$classes = $cache->classLocations;
		} catch (MemcacheException $e) {
			// you cannot set a memcache value to an empty array
			$classes = $cache->classLocations = array('Handler' => dirname(__FILE__) . '/Handler.php');
		} catch (CacheDisabledException $e)	{
			// do nothing
			$classes = array();
		}

		// look in the cache for the location -- if it's there, then we're done.  if not, we'll need to look for it elsewhere
		try {
			if (array_key_exists($className, $classes)) {
				if ($useInclude === true) {
					include($classes[$className]);
				} else {
					require_once($classes[$className]);
				}
			} else {
				throw new MemcacheException($className . ' has not been cached yet.');
			}
		} catch (MemcacheException $e) {
			// not in the cache so we'll need to find it ourselves
			$location = self::findClassPath($className);
			//file_put_contents(PATH_BASE . '/grr.txt', $location .  ' -- ' . $className . "\n", FILE_APPEND);
			if ($location !== false) {
				require_once($location);
				$classes[$className] = $location;
			}
			else {
				throw new AutoloadException('Unable to find file ' . $className . '.php');
			}
		}

		// save the class locations back to the classes
		try {
			$cache->classLocations = $classes;
		} catch (CacheDisabledException $e) {
			// do nothing
		}

		if (class_exists($className, false) === false && interface_exists($className, false) === false) {
			throw new AutoloadException($className . ' cannot be found.');
		}
	}
	
	public static function findClassPath($className) {
		global $paths;
		
		// the filename for the class should simply be the classname followed by .php
		$filename = $className . '.php';
		
		// look in app directory
		foreach ($paths['app'] as $key => $path) {
			$file = $path . '/' . $filename;
			if (file_exists($file)) {
				return $file;
			}
			if ($key == 'protocols') {
				$result = self::directorySearch($path, $filename);
				if ($result !== false) {
					return $result;
				}
			}
		}

		// look in shared directory if not found yet
		foreach ($paths['shared'] as $key => $val) {
			$file = $val . '/' . $filename;
			if (file_exists($file)) {
				return $file;
			}
			if ($key == 'protocols') {
				$result = self::directorySearch($val, $filename);
				if ($result !== false) {
					return $result;
				}
			}
		}

		// look in framework directory if not found yet
		foreach ($paths['framework'] as $key => $val) {
			$file = $val . '/' . $filename;
			if (file_exists($file)) {
				return $file;
			}
			if ($key == 'core') {
				$result = self::directorySearch($val, $filename);
				if ($result !== false) {
					return $result;
				}
			}
			if ($key == 'tests') {
				$result = self::directorySearch($val, $filename);
				if ($result !== false) {
					return $result;
				}
			}
		}
		
		// oops -- can't find it!
		return false;
	}

	public static function directorySearch($dirName, $filename) {
		$fileSPLObjects =  new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirName), RecursiveIteratorIterator::CHILD_FIRST);
		foreach($fileSPLObjects as $fullFileName => $fileSPLObject) {
			$currentFile = $fileSPLObject->getFilename();
			if ($currentFile == $filename) {
				return $fullFileName;
			}
		}
		return false;
	}

}
